
import java.io.*;
import java.net.*;
import java.util.Random;

public class TCPClient {

    static int count;
    String PeerID;
    String Filename;
    String New_Dir;
    String Filename_Dump;
    int FileSize;
    int PieceSize;
    int Num_of_pieces; 

    String PeerIDcheck;

    public TCPClient(String peerId) {
        PeerID = peerId; // should be coming from peerInfo
        New_Dir = "" ;
        Filename = "" ;
        
        Filename_Dump = "";

        FileSize = 1234; //read from config file
        PieceSize = 12; //read from config file
        Num_of_pieces = peerProcess.no_of_pieces; 
        System.out.println("TCP CLIENT CONSTR");
    }

    // create a class of these functions 
    
    // -1 indicates not available 1 indicates available
    
    public int getIndex(String byteString) {
    	int peers=peerProcess.no_of_peers;
    	if(peerProcess.no_of_peers==0)
    	{
    		//donothing
    	}
    	for (int i = 0; i < peerProcess.no_of_peers; i++) {
            if (peerProcess.peerinfo[i].PeerID.endsWith(byteString)) {
                return i;
            }
    	}
        return -1;
    }
    public static int IsSocketAvailable(int port_number) throws IOException {
    	// create a server socket connection
    	
    	ServerSocket Socket_Server = null;
        try{
    	Socket_Server = new ServerSocket(port_number);
    	Socket_Server.setReuseAddress(true);
            return 1;
        }catch (Exception e)
        {
        	System.out.println(e);
        }
        // exception might pop up , remember to add finally iff error pops up
        finally{ 
        if (Socket_Server != null) {
            	Socket_Server.close();
                }
        }      


        return -1;
    }

    public static void bitfield_generator() {
        
    		int count=0;
    		String bitfield = "";
    		char[] bitfield_array = bitfield.toCharArray();
            String[] filesList;
            java.io.File dir = new java.io.File("split");
            filesList = dir.list();
            int no_of_pieces = peerProcess.no_of_pieces;
            for (int i = 0; i < no_of_pieces; i++) {
            	
            	count++;
            }
            
            
            for (int i = 0; i < no_of_pieces; i++) {
                bitfield += '0';
            }

            	// populate bitfield array
            for (int i = 0; i < filesList.length; i++) {
                int index = Integer.parseInt(filesList[i].split("tempsplit")[1]);
                bitfield_array[index] = '1';
            }

           


        
    }

    public void AppendHeader(DataOutputStream transmitServer) throws IOException {
        // make sure to append the exact header address
    	String Header = "REPLACETHISTEXTWITHACTUALHEADERASPERSEPCIFICATION" + peerProcess.currentPeerId;
        
        transmitServer.writeBytes(Header + '\n');
    }

    public void AppendEnd(Socket clientSocket) throws IOException {
    	ObjectOutputStream outputStream = new ObjectOutputStream(clientSocket.getOutputStream());
    	String endPoint = "00019";
    	 byte[] endPoints = endPoint.getBytes();
    	

        outputStream.writeObject(endPoints);

        outputStream.flush();
    }

    // message INTERESTED
    public void SendInterested(Socket clientSocket) throws IOException {

    	// define bytenumber
        String Interest = "00012";
        byte[] INTERESTED = Interest.getBytes();
        ObjectOutputStream outputStream1 = new ObjectOutputStream(clientSocket.getOutputStream());
        outputStream1.writeObject(INTERESTED);
        outputStream1.flush();
    }

    // message NotInterested
    public void NotInterested(Socket clientSocket) throws IOException {
        String NotInterest = "00013";
        byte[] NOTINTERESTED = NotInterest.getBytes();
    	ObjectOutputStream outputStream2 = new ObjectOutputStream(clientSocket.getOutputStream());
        outputStream2.writeObject(NOTINTERESTED);
        outputStream2.flush();
    }

    public void Request(byte[] RequestByte, Socket clientSocket) throws IOException {
    	
        ObjectOutputStream outputStream3 = new ObjectOutputStream(clientSocket.getOutputStream());

        outputStream3.writeObject(RequestByte);

        outputStream3.flush();
    }

    public void Have(byte[] SentByte, Socket clientSocket) throws IOException {
    	ObjectOutputStream outputStream4 = new ObjectOutputStream(clientSocket.getOutputStream());

        outputStream4.writeObject(SentByte);

        outputStream4.flush();
    }
    
    // Added the 3 functions sendRequest, sendPiece, sendBitfield
    // index is the index of the requested piece
    public void sendRequest(Socket clientSocket, int index) throws IOException
    {
    	ObjectOutputStream os = new ObjectOutputStream(clientSocket.getOutputStream());
        
    	Packet packet = new Packet(Integer.parseInt(PeerID));
    	
    	os.writeObject(packet.createRequestPacket(index));
        os.flush();
    }
    
    // index is the index of the sent piece
    public void sendPiece(Socket clientSocket, int index) throws IOException
    {
    	ObjectOutputStream os = new ObjectOutputStream(clientSocket.getOutputStream());
        
    	Packet packet = new Packet(Integer.parseInt(PeerID));
    	
    	os.writeObject(packet.createPiecePacket(index));
        os.flush();
    }
    
    public void sendBitfield(Socket clientSocket, String bitfield) throws IOException
    {
    	ObjectOutputStream os = new ObjectOutputStream(clientSocket.getOutputStream());
        
    	Packet packet = new Packet(Integer.parseInt(PeerID));
    	
    	os.writeObject(packet.createBitfieldPacket(bitfield));
        os.flush();
    }
    
    
    
    // this function needs further improvements rather changes
    public void Client() {
    	System.out.println("client function starts");
        Socket clientSocket = null;
       
        InetAddress addr = null;
		try {
			addr = InetAddress.getLocalHost();
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}
        
       // String serverAddress = addr;// read from config file

        int serverPort = 6010;
        DataOutputStream transmitServer;
        BufferedReader inFromServer;
        String Header;
        String Headercheck;
        String ServerReply;
        String readServerHeader;

        
        Boolean isConnected = true;
        while (isConnected) {
            try {
                clientSocket = new Socket(addr, serverPort);
               
                //System.out.println(clientSocket.getPort());
                isConnected = false;
            } catch (Exception e) {
            	e.printStackTrace();
            }
        }
        

        try {
        	
        	System.out.println("Writing object");
        	transmitServer = new DataOutputStream(clientSocket.getOutputStream());
        	byte[] test = "this is a test".getBytes();
        	
        	transmitServer.write(test);
           
        	
        	

            
            int piece_count = Num_of_pieces;

            String FilePresent = "";
            for (int i = 0; i < piece_count; i++) {

                FilePresent += "1";
            }
            System.out.println("BIT FIELD GENERATOR");
            if (!FilePresent.equals(MainServer.Bitfield)) {

            	
                transmitServer = new DataOutputStream(clientSocket.getOutputStream());
                inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

                AppendHeader(transmitServer);

                readServerHeader = inFromServer.readLine();
                

                PeerIDcheck = readServerHeader.substring(28, 32);
               

                readServerHeader = inFromServer.readLine();
                String messagetype = "";
                messagetype = readServerHeader.substring(4, 5);

                if (messagetype.equals("5")) {
                    
                }


                String peer_has_pieces = readServerHeader.substring(5);
               // check updated PeerIDs from peer info
                int updateat = getIndex(PeerIDcheck);
                
                peerProcess.peerinfo[updateat].BitField = peer_has_pieces;
                
                int hh = 0, ctr = 0;
                // set choke to default value as false
                boolean CHOKE = false;

                while (piece_count > 0) {
                	// if not CHOKE 
                    if (!CHOKE) {

                   //     tempfile(PeerIDcheck);
                       
                        for (ctr = 0; ctr < peerProcess.no_of_pieces; ctr++) {
                            Random select_piece = new Random();
                            hh = select_piece.nextInt(Num_of_pieces);
                            if ((MainServer.Bitfield.charAt(hh) == '0') && (peerProcess.peerinfo[updateat].BitField.charAt(hh) == '1')) {
                                
                                break;
                            }
                        }

                      

                        if (ctr == peerProcess.no_of_pieces) {
                            
                            try {

                                ObjectOutputStream outputStream = new ObjectOutputStream(clientSocket.getOutputStream());

                                String bitfieldAgain = "00015";

                                byte[] bitfieldAgainByte = bitfieldAgain.getBytes();

                                System.out.println("Writing object");
                                
                                outputStream.writeObject(bitfieldAgainByte);

                                outputStream.flush();


                                ObjectInputStream ois12 = new ObjectInputStream(clientSocket.getInputStream());
                                byte[] buffer1 = (byte[]) ois12.readObject();
                                if (buffer1[0] == (byte) 6 && buffer1[1] == (byte) 6) {

                                    peerProcess.peerinfo[updateat].BitField = new String(buffer1).substring(2);

                                }
                            } catch (Exception e) {
                            }
                        } else {

                            if (FilePresent.equals(MainServer.Bitfield)) {
                                break;
                            }


                           // SendInterested(transmitServer, clientSocket);
                            SendInterested(clientSocket);
                            String send_server = MessageProcessor(hh, "6");

                            byte[] Sent_Request_to_Server = send_server.getBytes();

                            //Request(transmitServer, Sent_Request_to_Server, clientSocket);
                            Request(Sent_Request_to_Server, clientSocket);


                            ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream());
                            byte[] buffer = (byte[]) ois.readObject();
                            if (buffer[0] == (byte) 9 && buffer[1] == (byte) 9) {
                                FileOutputStream fos = new FileOutputStream(Filename_Dump + hh);
                                fos.write(buffer, 2, buffer.length - 2);
                            }

                            ObjectInputStream ois12 = new ObjectInputStream(clientSocket.getInputStream());

                            byte[] buffer1 = (byte[]) ois12.readObject();
                            if (buffer1[0] == (byte) 6 && buffer1[1] == (byte) 6) {

                                peerProcess.peerinfo[updateat].BitField = new String(buffer1).substring(2);

                            }

                            String Havemess = MessageProcessor(hh, "4");
                            byte[] SentByte = Havemess.getBytes();
                            Have(SentByte, clientSocket);

                            char[] ToChararray = MainServer.Bitfield.toCharArray();
                            ToChararray[hh] = '1';
                            MainServer.Bitfield = new String(ToChararray);
                            
                            int pieces_present=0;
                            for (int i = 0; i < Num_of_pieces; i++) {
                                if (MainServer.Bitfield.substring(i,i+1).equals("1"))
                                    pieces_present++;
                            }

                            // Recieve Choke Message
                            ObjectInputStream ois13 = new ObjectInputStream(clientSocket.getInputStream());
                            byte[] buffer2 = (byte[]) ois13.readObject();
                            if (buffer2[4] == (byte) 0 && buffer2[5] == (byte) 1) {
                                CHOKE = true;
                            }


                            for (hh = 0; hh < peerProcess.no_of_pieces; hh++) {
                                if ((MainServer.Bitfield.charAt(hh) == '0') && (peerProcess.peerinfo[updateat].BitField.charAt(hh) == '1')) {
                                    break;
                                }
                            }
                            if (hh == peerProcess.no_of_pieces) {
                                //NotInterested(transmitServer, clientSocket);
                            	NotInterested(clientSocket);
                            }

                            
                            if (FilePresent.equals(MainServer.Bitfield)) {
                               // AppendEnd(transmitServer, clientSocket);
                            	 AppendEnd(clientSocket);
                                piece_count = -1;
                                break;
                            }
                        }
                    } else {    // IF CHOKED - LOGIC FOR UNCHOKE
                        // Recieve Choke Message
                        ObjectInputStream ois13 = new ObjectInputStream(clientSocket.getInputStream());
                        byte[] buffer2 = (byte[]) ois13.readObject();
                        if (buffer2[3] == (byte) 1 && buffer2[4] == (byte) 1) {
                            CHOKE = false;
                        }
                    }

                }
                clientSocket.close();

                if (!peerProcess.fileMerging)
                {
                    peerProcess.fileMerging = true;
                    merge_chunks();
                }
               
            }
        } catch (Exception ex) {
        }

    }

////////////////////////////////////////////////////////////////////


    public static String MessageProcessor(int msgNumber, String messagetype) 
    {
    	// define payload and message that is to be send out
    	String payload="";
    	String messageToSend;
    	String requiredPayload;
    	String finalReqString;
    	String finalReqMessage;
    	
    	String msgString = Integer.toString(msgNumber);
    	int msgStringlength = msgString.length();   
            
            switch (msgStringlength)
            {
            case 1:
            	payload="000"+ msgString;
            	break;
            case 2:
            	payload="00"+ msgString;
            	break;
            case 3:
            	payload="0"+ msgString;
            	break;
            case 4:
            	payload= msgString;
            	break;
            	
            }
          
            String Temp_message=null;
            // create final payload
            requiredPayload = messagetype + payload;
            // repeat the same process as above for requiredPayload to create final message string
            int requiredPayloadLen = requiredPayload.length();
            finalReqString = Integer.toString(requiredPayloadLen);
            finalReqMessage = null;
            int req_length = finalReqString.length();
            
            switch (req_length)
            {
            case 1:
            	finalReqMessage="000"+ finalReqString;
            	break;
            case 2:
            	finalReqMessage="00"+ finalReqString;
            	break;
            case 3:
            	finalReqMessage="0"+ finalReqString;
            	break;
            case 4:
            	finalReqMessage= finalReqString;
            	break;
            }
           
            String Temp_payload=payload;
            Temp_message=finalReqMessage;
            messageToSend = finalReqMessage + requiredPayload;


        return messageToSend;
    }

 

    public int getIndexcfg(String byteString) {
        for (int i = 0; i < peerProcess.no_of_peers; i++) {
           // if (peer_cfg.peerID.elementAt(i).endsWith(byteString)) {
        	if (peer_cfg.peerID.get(i).endsWith(byteString)) {
                return i;
            }

        }
        return -1;
    }
   // merging chunks together
    
    // merge the file chunks over here
    public void merge_chunks() throws IOException {
       
        // get configurations from ConfigReader
        // get peerSize from the ConfigReaders
    	String filename=null;
    	int totalNumberofPieces;
        ConfigReader Cfgreader=new ConfigReader();
        int Piecesize = Cfgreader.pieceSize;
        byte Piecebuffer[] = new byte[Piecesize];
        int count = 0;
        
        // get total number of pieces
        totalNumberofPieces = peerProcess.no_of_pieces; 
        FileOutputStream fileOutputStream = new FileOutputStream(Filename);
                
        while (totalNumberofPieces > (count)) 
        {
            filename = Filename_Dump + count;
            File newfileObject = new File(filename);
            FileInputStream readerStream = new FileInputStream(filename);
            
            Piecesize = (int) newfileObject.length();
            // read stream, fill buffer and write buffer to the output stream
            readerStream.read(Piecebuffer, 0, Piecesize);
            fileOutputStream.write(Piecebuffer, 0, Piecesize);

            ++count;
            readerStream.close();
        }

        // can be flushed if some error is encountered
       // fileOutputStream.flush();

        fileOutputStream.close();
    }

}